﻿using System;
using System.Collections.Generic;
using System.IO;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Store;
using Rhino.Etl.Core;
using Rhino.Etl.Core.Operations;
using Version = Lucene.Net.Util.Version;

namespace Sqloogle.Operations {

    public class LuceneLoad : AbstractOperation {

        private readonly FSDirectory _indexDirectory;
        private readonly StandardAnalyzer _standardAnalyzer;
        private readonly IndexWriter _indexWriter;
        private readonly Dictionary<string, int> _counters = new Dictionary<string, int>();
        private readonly Dictionary<string, FieldInstructions> _fieldInstructions;

        public LuceneLoad(string folder, bool clean = false) {
            _indexDirectory = FSDirectory.Open(new DirectoryInfo(folder));
            _standardAnalyzer = new StandardAnalyzer(Version.LUCENE_30);
            _indexWriter = new IndexWriter(_indexDirectory, _standardAnalyzer, IndexWriter.MaxFieldLength.UNLIMITED);

            if (clean) {
                Info("Cleaning Lucene index at {0}", folder);
                _indexWriter.DeleteAll();
                _indexWriter.Commit();
            }

            _counters.Add("None", 0);
            _counters.Add("Create", 0);
            _counters.Add("Update", 0);

            _fieldInstructions = new Dictionary<string, FieldInstructions>();
            _fieldInstructions["id"] = new FieldInstructions(Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS);
            _fieldInstructions["use"] = new FieldInstructions(Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS);
            _fieldInstructions["score"] = new FieldInstructions(Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS);
            _fieldInstructions["sql"] = new FieldInstructions(Field.Store.NO, Field.Index.ANALYZED);
            _fieldInstructions["created"] = new FieldInstructions(Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS);
            _fieldInstructions["modified"] = new FieldInstructions(Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS);
            _fieldInstructions["lastused"] = new FieldInstructions(Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS);
            _fieldInstructions["lastneeded"] = new FieldInstructions(Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS);
        }

        public override IEnumerable<Row> Execute(IEnumerable<Row> rows) {
            foreach (var row in rows) {

                if (row["action"] == null) {
                    throw new InvalidOperationException("There is no action column.  A valid action is None, Create, or Update!");
                }

                var action = row["action"].ToString();
                row.Remove("action");

                _counters[action] += 1;

                switch (action) {
                    case "None":
                        continue;
                    case "Create":
                        _indexWriter.AddDocument(RowToDoc(row));
                        continue;
                    case "Update":
                        _indexWriter.DeleteDocuments(new Term("id", row["id"].ToString()));
                        _indexWriter.AddDocument(RowToDoc(row));
                        continue;
                }
            }
            yield break;
        }

        internal class FieldInstructions {
            public Field.Store Store { get; set; }
            public Field.Index Index { get; set; }
            public FieldInstructions(Field.Store store, Field.Index index) {
                Store = store;
                Index = index;
            }
        }

        private Document RowToDoc(Row row) {
            var doc = new Document();
            foreach (var column in row.Columns) {
                if (_fieldInstructions.ContainsKey(column)) {
                    doc.Add(new Field(column.ToLower(), row[column].ToString(), _fieldInstructions[column].Store, _fieldInstructions[column].Index));
                }
                else {
                    doc.Add(new Field(column.ToLower(), row[column].ToString(), Field.Store.YES, Field.Index.ANALYZED));
                }
            }
            return doc;
        }

        public sealed override void Dispose() {
            Info("Lucene Create: {0}, Update: {1}, and None: {2}.", _counters["Create"], _counters["Update"], _counters["None"]);

            Info("Lucene Optimizing.");
            _indexWriter.Optimize();

            Info("Lucene Committing.");
            _indexWriter.Commit();

            _indexWriter.Dispose();
            _indexDirectory.Dispose();
            _standardAnalyzer.Close();
            _standardAnalyzer.Dispose();

            base.Dispose();
        }
    }
}
